/**用缓存KEY：商品缓存KEY前缀 */
const SPEC_STOCK_QUANTITY_PREFIX = 'SPEC_STOCK_QUANTITY'; // 
/**用缓存KEY：库存售完消息通道 */
const LSQ_SPEC_CHL = 'LSQ_SPEC_CHL'; // 
/**用缓存KEY：商品更新消息通道 */
const USQ_SPEC_CHL = 'USQ_SPEC_CHL'; // 
/**用缓存KEY：订单编号序列值 */
const ORDER_SEQ = 'ORDER_SEQ';

const RedisClient = require('./redisclient');
const DbDriver = require('./dbdriver');
const moment = require('moment');

function info(msg) {
    console.info(`[${Date.now()}] - [${process.pid}] - ${msg}`);
}

function result(code, msg) {
    info(`${msg}-${code}`);
    return {
        errcode: code,
        errmsg: msg
    };
}

// 秒杀订单模拟服务
class ActivityService {
    constructor(config) {

        info(`启动订单服务，PID=${process.pid}`);

        /**用于一级缓存，存放商品已售罄的标志 */
        this.soldouts = {};
        /**用于二级缓存，存放商品库存 */
        this.cache = new RedisClient(config);
        /**用于事件发布 */
        this.publisher = new RedisClient(config);
        /**用于事件响应 */
        this.subscriber = new RedisClient(config);

        let driver = new DbDriver(config);
        driver.conn('fulihui-dev').then(db => {
            /**用于模拟商品和订单数据库 */
            this.db = db;

            info('注册缓存订阅事件');
            this.onInitEvents();

            info('服务启动时，先将商品库存缓存下来');
            this.onCacheStockQuantity();
        });
    }

    // 初始化分布式缓存
    async onInitEvents() {

        this.subscriber.client.subscribe(LSQ_SPEC_CHL);
        this.subscriber.client.subscribe(USQ_SPEC_CHL);

        this.subscriber.client.on('message', (chl, msg) => {
            if (!msg || msg.indexOf(':') < 0) return;
            let arr = msg.split(':');
            let goodsId = arr[1];
            let key = `${SPEC_STOCK_QUANTITY_PREFIX}_${goodsId}`;

            switch (chl) {
                case LSQ_SPEC_CHL: {
                    info(`收到售完通知消息:${msg}`);
                    let flag = arr[2];
                    if (parseInt(flag) == 1)
                        this.soldouts[goodsId] = true;
                    else if (this.soldouts[goodsId])
                        delete this.soldouts[goodsId];
                    break;
                }
                case USQ_SPEC_CHL: {
                    info(`收到商品更新的通知消息:${msg}`);
                    let sq = parseInt(arr[2]);
                    if (sq <= 0)
                        this.soldouts[goodsId] = true;
                    else if (this.soldouts[goodsId])
                        delete this.soldouts[goodsId];
                    this.cache.set(key, sq);
                    break;
                }
            }
        });
    }

    /**
     * 库存缓存
     * @param {bool} forceUpdate 强制将缓存中的库存设置（同步）为数据库的库存
     */
    async onCacheStockQuantity(forceUpdate) {
        let list = await this.db.collection('goods-test').find({}).toArray();
        list.forEach(s => {
            var key = `${SPEC_STOCK_QUANTITY_PREFIX}_${s.goodsId}`;
            if (forceUpdate) {
                this.cache.set(key, s.stockQuantity);
            }
            else {
                this.cache.client.exists(key, (err, reply) => {
                    if (reply !== 1) {
                        info(`缓存商品${s.goodsId}的初始库存=${s.stockQuantity}`);
                        this.cache.set(key, s.stockQuantity);
                    }
                });
            }
        });
    }

    // 重置库存数据以便进行压力测试
    async setup(goodsId) {
        this.cache.client.del('SERVICE_ERR_COUNTER');
        info('删除全部测试订单');
        await this.db.collection('goods-seckill-order-test').deleteMany({});
        info('设置商品初始库存');
        await this.db.collection('goods-test').deleteMany({});
        await this.db.collection('goods-test').insertOne({
            goodsId: goodsId,
            title: 'test goods',
            stockQuantity: 10,
        });
        await this.onCacheStockQuantity(true);
        return result(0, '已发送重置命令');
    }

    async genSeqNo(key) {
        let seq = await this.cache.incr(key);
        let orderNo = `${moment().format('YYYYMMDDHHmmss')}${seq}`;
        return orderNo;
    }

    // 秒杀接口调用
    async seckill(goodsId) {
        let key = `${SPEC_STOCK_QUANTITY_PREFIX}_${goodsId}`;
        if (this.soldouts[goodsId]) return result(100, '商品已秒完'); //  

        let orderNo = await this.genSeqNo(ORDER_SEQ);
        info(`准备处理订单请求-${orderNo}`);

        //TODO: 并发问题？？
        let sq = await this.cache.decr(key);
        info(`从二级缓存获取商品库存=${sq}`);
        if (sq < 0) {

            info('设置售完标志为true');
            this.soldouts[goodsId] = true;
            this.publisher.client.publish(LSQ_SPEC_CHL, `${process.pid}:${goodsId}:1`);

            info('将二级缓存里的库存恢复到0');
            await this.cache.incr(key);

            return result(101, '商品已售完');
        }
        try {
            info(`尝试创建订单-${orderNo}`);
            await this.placeOrder(goodsId, orderNo);
        } catch (ex) {
            info('下单失败，恢复缓存里的库存');
            await this.cache.incr(key);

            info('移除售完标志');
            delete this.soldouts[goodsId];
            this.publisher.client.publish(LSQ_SPEC_CHL, `${process.pid}:${goodsId}:0`);

            return result(102, ex);
        }

        info(`SERVICE_ERR_COUNTER:${await this.cache.get('SERVICE_ERR_COUNTER')}`);
        return result(0, '成功');
    }

    // 创建订单
    async placeOrder(goodsId, orderNo) {

        var rd = 10 * Math.random();
        if (rd > 5) {
            this.cache.incr('SERVICE_ERR_COUNTER');
            info(`服务意外down掉，将订单编号${orderNo}丢弃`);
            throw '服务异常';
        }

        info('下单信息存到数据库');
        let orderExists = null;
        try {
            orderExists = await this.db.collection('goods-seckill-order-test').findOne({ orderNo: orderNo });
        } catch (ex) {
            info(ex.message);
            throw '数据读取异常';
        }
        if (orderExists) throw '订单已创建过';

        try {
            await this.db.collection('goods-seckill-order-test').insertOne({
                orderNo: orderNo,
                goodsId: goodsId,
                pid: process.pid,
                quantity: 1,
                createdon: Date.now()
            }, async () => {
                info('更新库存到数据库');
                await this.db.collection('goods-test').findOneAndUpdate({ goodsId: goodsId }, { $inc: { stockQuantity: -1 } });
            });
        } catch (ex) {
            info(ex.message);
            throw '数据写入异常';
        }

        info(`订单${orderNo}创建完毕。`);
    }
}

module.exports = ActivityService;